/*
 * Server.java
 *
 */
package jhelp;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;

/**
 * This class sets a network connection between end client's objects
 * of {@link Client} type and single {@link jhelp.ServerDb} object.
 * @author <strong >Y.D.Zakovryashin, 2009</strong>
 * @version 1.0
 * @see Client
 * @see ClientThread
 * @see jhelp.ServerDb
 */
public class Server implements JHelp {
    public final static String DEFAULT_DB_HOST="localhost";

    /**
     *
     */
    private ServerSocket serverSocket;
    /**
     *
     */
    private Socket clientSocket;
    /**
     *
     */
    private ObjectInputStream input;
    /**
     *
     */
    private ObjectOutputStream output;

    private int myPort;
    private int dbPort;
    private String dbHost;

    /** Creates a new instance of Server */
    public Server() {
        this(DEFAULT_SERVER_PORT, DEFAULT_DATABASE_PORT);
        System.out.println("SERVER: Default Server Constructed");
    }

    /**
     *
     * @param port
     * @param dbPort
     */
    public Server(int port, int dbPort) {
        this.myPort = port;
        this.dbPort = dbPort;
        this.dbHost = DEFAULT_DB_HOST;
        System.out.println("SERVER: Server Constructed");
    }

    public Server(String dbHost, int port, int dbPort) {
        this(port, dbPort);
        this.dbHost = dbHost;
        System.out.println("SERVER: Server Constructed");
    }


    /**
     * 
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("SERVER: main");

        Server server;
        if (args.length >= 3) {
            int port = DEFAULT_SERVER_PORT;
            int dbPort = DEFAULT_DATABASE_PORT;
            try {
                port = Integer.parseInt(args[1]);
            } catch (NumberFormatException e) {
                System.err.println("Ошибочный номер порта сервера приложения, использую порт по умолчанию: "+port);
            }
            try {
                dbPort = Integer.parseInt(args[2]);
            } catch (NumberFormatException e) {
                System.err.println("Ошибочный номер порта сервера приложения, использую порт по умолчанию: " + dbPort);
            }
            server = new Server(args[0], port, dbPort);
        }
        else server = new Server();
        if (server.connect() == JHelp.OK) {
            server.run();
            server.disconnect();
        }
    }

    /**
     *
     */
    private void run() {
        if (serverSocket != null) {
            while (!Thread.currentThread().isInterrupted()) {

                try {
                    if (serverSocket.isClosed()) serverSocket = new ServerSocket(myPort);

                    Socket requestSession = serverSocket.accept();
                    System.out.println("SERVER: new session for client, addr "+requestSession.getRemoteSocketAddress());
                    new Thread(new ClientThread(this, requestSession)).start();
                } catch (IOException e) {
                    System.err.println("Сетевая ошибка при установлении новой сессии с клиентом");
                    e.printStackTrace();
                    try {
                        serverSocket.close();
                    } catch (IOException e1) {
                        e1.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * The method sets connection to database ({@link jhelp.ServerDb} object) and
     * create {@link java.net.ServerSocket} object for waiting of client's
     * connection requests. This method uses default parameters for connection.
     * @return error code. The method returns {@link jhelp.JHelp#OK} if streams are
     * successfully opened, otherwise the method returns {@link jhelp.JHelp#ERROR}.
     */
    public int connect() {
        try {
            clientSocket = new Socket(dbHost, dbPort);
            input = new ObjectInputStream(clientSocket.getInputStream());
            output = new ObjectOutputStream(clientSocket.getOutputStream());
            clientSocket.setKeepAlive(true);
        } catch (IOException e) {
            System.err.println("Невозможно установить соединение с сервером БД: "+dbHost+":"+dbPort);
            e.printStackTrace();
            return JHelp.ERROR;
        }
        try {
            if (serverSocket != null && !serverSocket.isClosed()) serverSocket.close();
            serverSocket = new ServerSocket(myPort);
        } catch (IOException e) {
            System.err.println("Невозможно открыть серверный сокет: "+myPort);
            e.printStackTrace();
            disconnect();
            return JHelp.ERROR;
        }

        System.out.println("SERVER: connect");
        return OK;
    }

    /**
     * The method sets connection to database ({@link jhelp.ServerDb} object) and
     * create {@link java.net.ServerSocket} object for waiting of client's
     * connection requests.
     * @param args specifies properties of connection.
     * @return error code. The method returns {@link jhelp.JHelp#OK} if connection are
     * openeds uccessfully, otherwise the method returns {@link jhelp.JHelp#ERROR}.
     */
    public int connect(String[] args) {
        return connect();
    }

    /**
     * Transports initial {@link jhelp.Data} object from {@link jhelp.ClientThread} object to
     * {@link ServerDb} object and returns modified {@link jhelp.Data} object to
     * {@link jhelp.ClientThread} object.
     * @param data Initial {@link jhelp.Data} object which was obtained from client
     * application.
     * @return modified {@link jhelp.Data} object
     */
    public Data getData(Data data) {
        Data response = new Data(JHelp.ERROR, null, null);
        Object inObj;
        if (output != null && input != null) {
            try {
                output.writeObject(data);
                System.out.println("Send Data: "+data);
                if (data.getOperation() == JHelp.DISCONNECT) return new Data(JHelp.DISCONNECT, null, null);
                inObj = input.readObject();
                if (inObj != null && inObj instanceof Data) {
                    response = (Data)inObj;
                }
            } catch (SocketException e) {
                System.err.println("Обнаружен разрыв соединения с сервером БД, восстанавливаю соединение.");
                if (connect() != JHelp.OK) {
                    System.err.println("Не удается восстановить соединение с БД");
                }
            } catch (IOException e) {
                System.err.println("Сетевая ошибка");
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                System.err.println("Неизвестный ответ от сервера БД");
                e.printStackTrace();
            }
        }
//        System.out.println("SERVER:getData");
        return response;
    }

    /**
     * The method closes connection with database.
     * @return error code. The method returns {@link jhelp.JHelp#OK} if a connection
     * with database ({@link ServerDb} object) closed successfully,
     * otherwise the method returns {@link jhelp.JHelp#ERROR} or any error code.
     */
    public int disconnect() {
        getData(new Data(JHelp.DISCONNECT, null, null));
        try {
            if (serverSocket != null && !serverSocket.isClosed()) serverSocket.close();
            if (output != null) output.close();
            if (input != null) input.close();
            if (clientSocket != null && !clientSocket.isClosed()) clientSocket.close();
        } catch (IOException e) {
            System.err.println("Ошибка при закрытии соединений");
            e.printStackTrace();
            return JHelp.ERROR;
        }
        System.out.println("SERVER: disconnect");
        return DISCONNECT;
    }
}
